#include "console.h"
#include "misc/strutil.h"
#include "debug/logger.h"

namespace renderit
{
	namespace client
	{
		console::console(const std::string& name)
		{
			name_ = name;

			DWORD oerr = 0, ierr = 0;
			houtput_ = ::GetStdHandle(STD_OUTPUT_HANDLE);
			if (!houtput_ || houtput_ == INVALID_HANDLE_VALUE)
			{
				oerr = ::GetLastError();
				houtput_ = nullptr;
			}

			hinput_ = ::GetStdHandle(STD_INPUT_HANDLE);
			if (!hinput_ || hinput_ == INVALID_HANDLE_VALUE)
			{
				ierr = ::GetLastError();
				hinput_ = nullptr;
			}

			if (houtput_ && hinput_)
			{
				logger::info("Create Console Success!");
			}
			else
			{
				logger::warn("Create Console Failed, output(%d), input(%d)", oerr, ierr);
			}
		}

		math::coord console::curr_pos() const
		{
			if (!houtput_)
			{
				return math::coord();
			}

			CONSOLE_SCREEN_BUFFER_INFO csbi;
			BOOL b = ::GetConsoleScreenBufferInfo(houtput_, &csbi);
			return math::coord(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y);
		}

		bool console::set_curr_pos(const math::coord& pos)
		{
			if (!houtput_)
			{
				return false;
			}

			COORD coord = { pos.x, pos.y };
			BOOL b = ::SetConsoleCursorPosition(houtput_, coord);
			return b;
		}

		iconsole * console::write(const std::string& content)
		{
			if (!houtput_)
			{
				return this;
			}

			DWORD nwritten;
			BOOL b = ::WriteConsole(houtput_, content.c_str(), content.length(), &nwritten, nullptr);
			return this;
		}

		iconsole * console::write(const char * format, ...)
		{
			va_list va;
			va_start(va, format);

			std::string content = strutil::str_format(format, va);
			write(content);
			return this;
		}

		void console::clear_line()
		{
			if (!houtput_)
			{
				return;
			}
			CONSOLE_SCREEN_BUFFER_INFO csbi;
			::GetConsoleScreenBufferInfo(houtput_, &csbi);

			COORD coord = { 0, csbi.dwCursorPosition.Y };
			DWORD nwritten;

			::FillConsoleOutputCharacter(houtput_, ' ', csbi.dwSize.Y, coord, &nwritten);
			::SetConsoleCursorPosition(houtput_, coord);
		}

		void console::back_line()
		{
			if (!houtput_)
			{
				return;
			}

			clear_line();

			math::coord p = curr_pos();
			if (p.y > 1)
			{
				p.y--;
				set_curr_pos(p);
				clear_line();
			}
		}

		static bool is_readable(char c)
		{
			if (c >= 0x20 && c <= 0x7E)
			{
				return true;
			}
			if (c == '\r' || c == '\n' || c == '\t' || c == '\b')
			{
				return true;
			}
			return false;
		}

		int console::peek_input()
		{
			if (!hinput_)
			{
				return 0;
			}

			INPUT_RECORD input[128];

			DWORD nread = 0;

			BOOL b = ::PeekConsoleInput(hinput_, input, 128, &nread);

			if (!b || !nread)
			{
				return 0;
			}

			::ReadConsoleInput(hinput_, input, 128, &nread);

			for (int i = 0; i < nread; ++i)
			{
				INPUT_RECORD & ir = input[i];
				switch (ir.EventType)
				{
				case KEY_EVENT:
					if (ir.Event.KeyEvent.bKeyDown)
					{
						char b[2] = { 0 };
						b[0] = ir.Event.KeyEvent.uChar.AsciiChar;
						if (!is_readable(b[0]))
						{
							break;
						}

						if (b[0] == '\r')
						{
							b[0] = '\n';
							cached_inputs_.push_back(std::move(curr_input_));
							write(b);
							break;
						}
						
						if (b[0] == '\b')
						{
							if (curr_input_.size())
							{
								curr_input_.erase(curr_input_.size() - 1);
								clear_line();
								write(curr_input_);
							}
						}
						else
						{
							curr_input_.push_back(b[0]);
							write(b);
						}						
					}
					break;

				default:
					break;
				}

			}

			return cached_inputs_.size();
		}

		void console::do_input()
		{
			int nline = peek_input();
			if (nline)
			{
				std::vector<std::string> lines = get_inputs();
				for (auto& i : lines)
				{
					// Do Command
					if (input_listener_)
					{
						input_listener_(i);
					}
				}
			}
		}
	}
}